﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections.Concurrent;
using SharpShare.Collections;

namespace SharpShare.Storage {
    public static class StorageLookup {
        public static IStorageLookup ToStorageLookup(this IEnumerable<IStorageItem> items, int? count = null) {
            return new ListStorageLookup(items, count);
        }
        public static IStorageLookup ToStorageLookup<TToken>(this IEnumerable<TToken> tokens, Func<string, TToken> tokenSelector, Func<TToken, IStorageItem> itemSelector) {
            return new TokenStorageLookup<TToken>(tokens, tokenSelector, itemSelector);
        }
        public static IStorageLookup Where(this IStorageLookup baseLookup, Func<IStorageItem, bool> predicate) {
            return new FilteredStorageLookup(baseLookup, predicate);
        }
        public static IStorageLookup Replace(this IStorageLookup baseLookup, Func<IStorageItem, IStorageItem> selector) {
            return new ReplaceStorageLookup(baseLookup, selector);
        }
        public static IStorageLookup Attach(this IStorageLookup baseLookup, Func<IStorageItem, IEnumerable<IStorageItem>> selector) {
            return ListAttach(baseLookup, selector).ToStorageLookup(baseLookup.Count);
        }
        public static IStorageLookup ChangeParent(this IStorageLookup baseLookup, IStorageContainer newParent) {
            return new ParentChangingStorageLookup(baseLookup, newParent);
        }

        private static IEnumerable<IStorageItem> ListAttach(IStorageLookup baseLookup, Func<IStorageItem, IEnumerable<IStorageItem>> selector) {
            foreach (IStorageItem item in baseLookup) {
                yield return item;

                foreach (IStorageItem attachItem in selector(item)) {
                    yield return attachItem;
                }
            }
        }

        private sealed class ParentChangingStorageLookup : IStorageLookup {
            private IStorageLookup _baseLookup;
            private IStorageContainer _newParent;

            public ParentChangingStorageLookup(IStorageLookup baseLookup, IStorageContainer newParent) {
                if (baseLookup == null) throw new ArgumentNullException("baseLookup");

                _baseLookup = baseLookup;
                _newParent = newParent;
            }

            private IEnumerable<IStorageItem> ListChangedParentContents() {
                foreach (IStorageItem item in _baseLookup) {
                    yield return item.ChangeParent(_newParent);
                }
            }

            #region IStorageLookup Members

            public int Count {
                get { return _baseLookup.Count; }
            }

            public IStorageItem this[string name] {
                get {
                    IStorageItem item = _baseLookup[name];

                    if (item == null) {
                        return null;
                    }

                    return item.ChangeParent(_newParent);
                }
            }

            #endregion

            #region IEnumerable<IStorageItem> Members

            public IEnumerator<IStorageItem> GetEnumerator() {
                return this.ListChangedParentContents().GetEnumerator();
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                return this.GetEnumerator();
            }

            #endregion
        }

        //private sealed class AttachStorageLookup : IStorageLookup {
        //    private IStorageLookup _baseLookup;
        //    private Func<IStorageItem, IEnumerable<IStorageItem>> _selector;
        //    private ConcurrentDictionary<IStorageItem, IList<IStorageItem>> _attachments = new ConcurrentDictionary<IStorageItem, IList<IStorageItem>>();

        //    public AttachStorageLookup(IStorageLookup baseLookup, Func<IStorageItem, IEnumerable<IStorageItem>> selector) {
        //        if (baseLookup == null) throw new ArgumentNullException("baseLookup");
        //        if (selector == null) throw new ArgumentNullException("selector");

        //        _baseLookup = baseLookup;
        //        _selector = selector;
        //    }

        //    #region IStorageLookup Members

        //    public int Count {
        //        get { return _baseLookup.Count; }
        //    }

        //    public IStorageItem this[string name] {
        //        get {
        //        istoragei
        //        }
        //    }

        //    #endregion

        //    #region IEnumerable<IStorageItem> Members

        //    public IEnumerator<IStorageItem> GetEnumerator() {
        //        throw new NotImplementedException();
        //    }

        //    #endregion

        //    #region IEnumerable Members

        //    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        //        throw new NotImplementedException();
        //    }

        //    #endregion

        //    private IList<IStorageItem> GetAttachments(IStorageItem item) {
        //        return _attachments.GetOrAdd(
        //            item,
        //            k => _selector(k).ToList());
        //    }
        //}
        private sealed class ReplaceStorageLookup : IStorageLookup {
            private IStorageLookup _baseLookup;
            private Func<IStorageItem, IStorageItem> _selector;
            private ConcurrentDictionary<IStorageItem, IStorageItem> _replacements = new ConcurrentDictionary<IStorageItem, IStorageItem>();

            public ReplaceStorageLookup(IStorageLookup baseLookup, Func<IStorageItem, IStorageItem> selector) {
                if (baseLookup == null) throw new ArgumentNullException("baseLookup");
                if (selector == null) throw new ArgumentNullException("selector");

                _baseLookup = baseLookup;
                _selector = selector;
            }

            #region IStorageLookup Members

            public int Count {
                get { return _baseLookup.Count; }
            }

            public IStorageItem this[string name] {
                get {
                    IStorageItem item = _baseLookup[name];

                    if (item == null) {
                        return null;
                    }

                    IStorageItem replacement =
                        _replacements.GetOrAdd(item, i => _selector(i)) ?? item;

                    return replacement;
                }
            }

            #endregion

            private IEnumerable<IStorageItem> List() {
                foreach (IStorageItem item in _baseLookup) {
                    yield return (_replacements.GetOrAdd(item, i => _selector(i)) ?? item);
                }
            }

            #region IEnumerable<IStorageItem> Members

            public IEnumerator<IStorageItem> GetEnumerator() {
                return this.List().GetEnumerator();
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                return this.GetEnumerator();
            }

            #endregion
        }

        private sealed class FilteredStorageLookup : IStorageLookup {
            private IStorageLookup _baseLookup;
            private Func<IStorageItem, bool> _predicate;

            public FilteredStorageLookup(IStorageLookup baseLookup, Func<IStorageItem, bool> predicate) {
                if (baseLookup == null) throw new ArgumentNullException("baseLookup");
                if (predicate == null) throw new ArgumentNullException("predicate");

                _baseLookup = baseLookup;
                _predicate = predicate;
            }

            #region IStorageLookup Members

            public int Count {
                get { return _baseLookup.Count; }
            }

            public IStorageItem this[string name] {
                get {
                    IStorageItem item = _baseLookup[name];

                    if (item == null) {
                        return null;
                    }

                    if (!_predicate(item)) {
                        return null;
                    }

                    return item;
                }
            }

            #endregion

            #region IEnumerable<IStorageItem> Members

            public IEnumerator<IStorageItem> GetEnumerator() {
                return ((IEnumerable<IStorageItem>)_baseLookup).Where(_predicate).GetEnumerator();
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                return this.GetEnumerator();
            }

            #endregion
        }

        private sealed class TokenStorageLookup<TToken> : IStorageLookup {
            private ConcurrentHashSet<TToken> _unresolvedTokens = new ConcurrentHashSet<TToken>();
            private StorageItemCollection _resolvedTokens = new StorageItemCollection();
            private Func<string, TToken> _tokenSelector;
            private Func<TToken, IStorageItem> _itemSelector;
            private object _lock = new object();
            private int _count;

            public TokenStorageLookup(IEnumerable<TToken> tokens, Func<string, TToken> tokenSelector, Func<TToken, IStorageItem> itemSelector) {
                if (tokens == null) throw new ArgumentNullException("tokens");
                if (tokenSelector == null) throw new ArgumentNullException("tokenSelector");
                if (itemSelector == null) throw new ArgumentNullException("itemSelector");

                _unresolvedTokens.Union(tokens);
                _count = _unresolvedTokens.Count;

                _tokenSelector = tokenSelector;
                _itemSelector = itemSelector;
            }

            #region IStorageLookup Members

            public int Count {
                get { return _count; }
            }

            public IStorageItem this[string name] {
                get {
                    lock (_lock) {
                        if (_resolvedTokens.Contains(name)) {
                            return _resolvedTokens[name];
                        }

                        TToken token = _tokenSelector(name);

                        IStorageItem item = _itemSelector(token);

                        if (item == null) {
                            return null;
                        }

                        if (_unresolvedTokens.Remove(token)) {
                            _resolvedTokens.Add(item);
                        }

                        return item;
                    }
                }
            }

            #endregion

            private IEnumerable<IStorageItem> ListWhileResolving() {
                IList<IStorageItem> resolved;

                lock (_lock) {
                    resolved = _resolvedTokens.ToList();
                }

                foreach (IStorageItem item in resolved) {
                    yield return item;
                }

                foreach (TToken unresolved in _unresolvedTokens) {
                    if (_unresolvedTokens.Remove(unresolved)) {
                        IStorageItem newItem = _itemSelector(unresolved);
                        if (newItem == null) {
                            continue;
                        }

                        lock (_lock) {
                            _resolvedTokens.Add(newItem);
                        }

                        yield return newItem;
                    }
                }
            }

            #region IEnumerable<IStorageItem> Members

            public IEnumerator<IStorageItem> GetEnumerator() {
                return ListWhileResolving().GetEnumerator();
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                return this.GetEnumerator();
            }

            #endregion
        }

        private sealed class ListStorageLookup : IStorageLookup {
            private StorageItemCollection _discoveredItems = new StorageItemCollection();
            private IEnumerator<IStorageItem> _enumerator;
            private int? _count;
            private object _lock = new object();

            public ListStorageLookup(IEnumerable<IStorageItem> items, int? countHint = null) {
                if (items == null) throw new ArgumentNullException("items");

                _enumerator = items.GetEnumerator();
                _count = countHint;
            }

            #region IStorageLookup Members

            public int Count {
                get {
                    lock (_lock) {
                        if (!_count.HasValue) {
                            this.PullRemainingItems().ToList();
                            return Count;
                        }

                        return (_count.Value + _discoveredItems.Count);
                    }
                }
            }

            public IStorageItem this[string name] {
                get {
                    lock (_lock) {
                        if (_discoveredItems.Contains(name)) {
                            return _discoveredItems[name];
                        }

                        IStorageItem item = this.PullRemainingItems()
                            .Where(i => i.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
                            .FirstOrDefault();

                        return item;
                    }
                }
            }

            #endregion

            private IEnumerable<IStorageItem> PullRemainingItems() {
                while (_enumerator.MoveNext()) {
                    IStorageItem item = _enumerator.Current;
                    lock (_lock) {
                        _discoveredItems.Add(item);
                        if (_count.HasValue && _count.Value > 0) {
                            _count = (_count.Value - 1);
                        }
                    }
                    yield return item;
                }

                _count = 0;
            }

            private IEnumerable<IStorageItem> ListContents() {
                IList<IStorageItem> discovered;

                lock (_lock) {
                    discovered = _discoveredItems.ToList();
                }

                foreach (IStorageItem item in discovered) {
                    yield return item;
                }

                foreach (IStorageItem item in this.PullRemainingItems()) {
                    yield return item;
                }
            }

            #region IEnumerable<IStorageItem> Members

            public IEnumerator<IStorageItem> GetEnumerator() {
                return ListContents().GetEnumerator();
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                return this.GetEnumerator();
            }

            #endregion
        }
    }
}
